
using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using CommonTypes;
//using Ingenious.Mvc.Configuration;
//using Ingenious.Mvc.Util;

namespace BuildingBasicDomain
{
	/// <include file='DefaultFactoryBase.doc.xml' path='/doc/member[@name="T:DefaultFactoryBase"]/*'/>
	[Serializable]
	public class DefaultFactoryBase
	{

        private static DefaultFactoryBase _instance;
        public static DefaultFactoryBase Instance
        {
            get
            {
 
                    if (_instance == null)
                    {
                        _instance = new DefaultFactoryBase();
                    }
                return _instance;
            }
        }


		private const BindingFlags _propertyBindingFlags = BindingFlags.Instance | BindingFlags.Public;
		private const BindingFlags _constructorBindingFlags = BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public;

		/// <include file='DefaultFactoryBase.doc.xml' path='/doc/member[@name="P:Name"]/*'/>
		public static   string Name
		{
			get{return "fuctory";}
		}

		/// <include file='DefaultFactoryBase.doc.xml' path='/doc/member[@name="M:CreateInstance(System.Type,System.Object,System.Type[],System.Object[],System.String[],System.Type[])"]/*'/>
		public static object CreateInstance(Type type, Type[] argTypes, object[] args, string[] argNames)
		{
			ArgumentHelper.AssertNotNull(type, "type");

			if (argTypes == null)
			{
				//default to zero arguments
				argTypes = Type.EmptyTypes;
			}
			else
			{
				//all supplied argument types must be non-null
				foreach (object argType in argTypes)
				{
					ExceptionHelper.ThrowIf(argType == null, "CreateInstance.nullArgumentType", "argTypes");
				}
			}

			if (argNames == null)
			{
				//default to zero argument names
				argNames = new string[0];
			}
			else
			{
				//all supplied argument names must be non-null
				foreach (object argName in argNames)
				{
					ExceptionHelper.ThrowIf(argName == null, "CreateInstance.nullArgumentName", "argNames");
				}
			}

			if (args == null)
			{
				//default to zero arguments
				args = new object[0];
			}

			//make sure the same number of argument types, argument values and argument names were supplied
			ExceptionHelper.ThrowIf(argTypes.Length != args.Length, "CreateInstance.argumentTypesAndValuesMismatch", argTypes.Length, args.Length);
			ExceptionHelper.ThrowIf(args.Length != argNames.Length, "CreateInstance.argumentAndNamesMismatch", args.Length, argNames.Length);


			object retVal = null;
			Binder binder = Type.DefaultBinder;
			object state;
			//first we'll look for a constructor that takes the arguments in question
			ConstructorInfo constructor = null;
			//this is set to true if all properties are successfully passed into an appropriate constructor
			bool propertiesPassedInConstructor = false;
			
			if (type.GetConstructors(_constructorBindingFlags).Length > 0)
			{
				try
				{
					constructor = (ConstructorInfo) binder.BindToMethod(_constructorBindingFlags, type.GetConstructors(_constructorBindingFlags), ref args, null, null, null, out state);
					propertiesPassedInConstructor = true;
				}
				catch (MissingMethodException)
				{
					//swallow
				}
			}

			//no constructor was found that takes the necessary arguments - look for a default constructor
			if (constructor == null)
			{
				constructor = type.GetConstructor(new Type[0]);
			}

			ExceptionHelper.ThrowIf(constructor == null, "CreateInstance.noAppropriateConstructor", type.FullName, Name, GetArgList(argTypes));

			//now try and create an instance of the type via the constructor we found
			try
			{
				retVal = constructor.Invoke(propertiesPassedInConstructor ? args : new object[0]);
			}
			catch (Exception e)
			{
				ExceptionHelper.Throw("CreateInstance.constructorInvokeFailed", e, type.FullName, Name);
			}

			//set properties if we couldn't find a constructor to do it for us
			if (!propertiesPassedInConstructor)
			{
				for (int i = 0; i < args.Length; ++i)
				{
					object arg = args[i];
					string argName = argNames[i];
					//try the obvious match first
					PropertyInfo property = type.GetProperty(argName, argTypes[i]);
					
					if (property == null)
					{
						try
						{
							property = binder.SelectProperty(_propertyBindingFlags, type.GetProperties(_propertyBindingFlags), arg.GetType(), Type.EmptyTypes, null);
						}
						catch (Exception e)
						{
							ExceptionHelper.Throw("CreateInstance.problemFindingProperty", e, arg.GetType().FullName, type.FullName);
						}
					}

					ExceptionHelper.ThrowIf(property == null, "CreateInstance.noAppropriateProperty", type.FullName, GetArgList(argTypes), GetPropertySignature(argTypes[i]), Name);
					ExceptionHelper.ThrowIf(!property.CanWrite, "CreateInstance.propertyNotWritable", property.Name, type.FullName);

					//set the property
					property.SetValue(retVal, arg, null);
				}
			}

	

			return retVal;
		}

		private static string GetArgList(Type[] argTypes)
		{
			StringBuilder retVal = new StringBuilder();

			retVal.Append("(");

			foreach (Type argType in argTypes)
			{
				retVal.Append(argType).Append(", ");
			}

			//remove last comma and space after it
			if (argTypes.Length > 0)
			{
				retVal.Remove(retVal.Length - 2, 2);
			}

			retVal.Append(")");

			return retVal.ToString();
		}

		private static string GetPropertySignature(Type type)
		{
			return string.Concat("public ", type.Name, " PropertyName { get; set; }");
		}
	}
}
